/*
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 *
 * The Initial Developer of the Original Code is iSQL-Viewer, A Mutli-Platform Database Tool.
 * Portions created by Mark A. Kobold are Copyright (C) 2000-2007. All Rights Reserved.
 *
 * Contributor(s): 
 *  Mark A. Kobold [mkobold <at> isqlviewer <dot> com].
 *  
 * If you didn't download this code from the following link, you should check
 * if you aren't using an obsolete version: http://www.isqlviewer.com
 */
package org.isqlviewer.model;

import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TreeMap;
import java.util.Vector;

import javax.swing.event.TreeModelEvent;
import javax.swing.tree.TreePath;

import org.isqlviewer.JdbcCommandLogger;
import org.isqlviewer.history.HistoricalCommand;
import org.isqlviewer.sql.embedded.EmbeddedDatabase;
import org.isqlviewer.swing.outline.AbstractTreeTableModel;
import org.isqlviewer.swing.outline.OutlineModel;

/**
 * Model for displaying the historical events in a tree fashion.
 * <p>
 * This model is a two tier tree such that there are date nodes refere to an internal collection historical reference
 * nodes.
 * 
 * @author Mark A. Kobold &lt;mkobold at isqlviewer dot com&gt;
 * @see org.isqlviewer.history.HistoryReference
 * @version 1.0
 */
public final class HistoryTreeModel extends AbstractTreeTableModel implements JdbcCommandLogger {

    private static final Class[] columnTypes = {OutlineModel.class, Date.class};

    private final TreeMap<Date, List<HistoricalCommand>> rootNode = new TreeMap<Date, List<HistoricalCommand>>();
    private final List<Date> rootKeySet = new Vector<Date>();

    public HistoryTreeModel() {

    }

    public void addReference(HistoricalCommand reference) {

        Date normalizedDate = normalizeTimestamp(reference.getQueryTime());
        List<HistoricalCommand> set = null;
        synchronized (rootNode) {
            set = rootNode.get(normalizedDate);
            if (set == null) {
                set = new Vector<HistoricalCommand>();
                rootNode.put(normalizedDate, set);
                rootKeySet.add(normalizedDate);
                reload();
            }
            set.add(reference);
        }
        Object[] objectPath = new Object[]{rootNode, normalizedDate};
        Object[] children = new Object[]{reference};
        int[] indicies = new int[]{set.size() - 1};
        fireTreeNodesInserted(new TreeModelEvent(this, objectPath, indicies, children));
    }

    /**
     * Empties this tree model of all internal references to other objects.
     * <p>
     * fairly run of the mill clearing function.
     */
    public synchronized void clear() {

        rootNode.clear();
        rootKeySet.clear();
        reload();
    }

    public Object getRoot() {

        return rootNode;
    }

    public Object getChild(Object parent, int index) {

        if (parent == rootNode) {
            return rootKeySet.get(index);
        } else if (parent instanceof Date) {
            List< ? > list = rootNode.get(parent);
            return list == null ? null : list.get(index);
        } else if (parent instanceof HistoricalCommand) {
            HistoricalCommand reference = (HistoricalCommand) parent;
            return reference.getChildCommand(index);
        }
        return null;
    }

    public int getChildCount(Object parent) {

        if (parent == rootNode) {
            return rootKeySet.size();
        } else if (parent instanceof Date) {
            List< ? > list = rootNode.get(parent);
            return list == null ? 0 : list.size();
        } else if (parent instanceof HistoricalCommand) {
            HistoricalCommand reference = (HistoricalCommand) parent;
            return reference.getChildCount();
        }
        return 0;
    }

    public boolean isLeaf(Object node) {

        if (node instanceof HistoricalCommand) {
            HistoricalCommand reference = (HistoricalCommand) node;
            return !reference.hasChildCommands();
        }
        return false;
    }

    public void valueForPathChanged(TreePath path, Object newValue) {

    }

    public int getIndexOfChild(Object parent, Object child) {

        if (parent == rootNode) {
            return rootKeySet.indexOf(child);
        } else if (parent instanceof Date) {
            List< ? > list = rootNode.get(parent);
            return list == null ? -1 : list.indexOf(child);
        } else if (parent instanceof HistoricalCommand) {
            HistoricalCommand reference = (HistoricalCommand) parent;
            HistoricalCommand subCommand = (HistoricalCommand) child;
            return reference.indexOf(subCommand);
        }
        return -1;
    }

    public Class getColumnClass(int column) {

        return columnTypes[column];
    }

    public int getColumnCount() {

        return 2;
    }

    public String getColumnName(int column) {

        switch (column) {
            case 0 :
                return "Command";
            case 1 :
                return "Time";
            default :
                return null;
        }
    }

    public Object getValueAt(Object node, int column) {

        if (node == rootNode) {
            return rootNode;
        } else if (column == 0 && node instanceof Date) {
            return node;
        }
        return node;
    }

    public synchronized void logCommand(HistoricalCommand command) {

        EmbeddedDatabase edb = EmbeddedDatabase.getSharedInstance();
        try {
            HistoricalCommand reference = edb.addHistoricalCommand(command);
            addReference(reference);
        } catch (SQLException e) {
        }
    }

    @Override
    public void setValueAt(Object aValue, Object node, int column) {

    }

    // Normalizes the timestamp to the zero hour minute and seconds of the day//
    private Date normalizeTimestamp(Date timestamp) {

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(timestamp);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        Date branchDate = calendar.getTime();
        return branchDate;
    }
}